﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GeneticAlgorithm
{
    // Тип полигона (равносторонний треугольник, квадрат, круг)
    public enum PolygonType
    {
        Triangle,
        Square,
        Circle
    }

    // Полигон 
    public class Polygon : ICloneable
    {
        public PolygonType Type { get; set; }
        public Point Center { get; set; }
        public int Radius { get; set; }
        public double Angle { get; set; }
        public Brush Brush { get; set; }

        public void SetRandom()
        {

            //Задаем тип полигона
            switch (Constants.Rand.Next(0,3))
            {
                case 0:
                    Type = PolygonType.Circle;
                    break;
                case 1:
                    Type = PolygonType.Square;
                    break;
                case 2:
                    Type = PolygonType.Triangle;
                    break;
                default:
                    break;
            }

            //Задаем центр
            Center = new Point(Constants.Rand.Next(0, Constants.Width),Constants.Rand.Next(0, Constants.Height));
            
            //Задаем радиус
            Radius = Constants.Rand.Next(0, Math.Min(Constants.Height,Constants.Width));

            //Задаем угол 
            Angle = Constants.Rand.NextDouble() * Math.PI * 2;

            Brush = new Brush();
            Brush.SetRandom();
        }

        public void Mutate(Workarea workarea)
        {
            if (Constants.Rand.NextDouble() <= Constants.MutationChangeTypeChance)
            {
                //Может и не меняем
                switch (Constants.Rand.Next(0, 3))
                {
                    case 0:
                        Type = PolygonType.Circle;
                        break;
                    case 1:
                        Type = PolygonType.Square;
                        break;
                    case 2:
                        Type = PolygonType.Triangle;
                        break;
                    default:
                        break;
                }
            }

            Center = MutatePoint(Center, workarea);

            Radius = MutateRadius(Radius, workarea);

            Angle = MutateAngle(Angle, workarea);

            Brush.Mutate(workarea);            
        }
   
        public void Draw(Graphics g)
        {
            using (SolidBrush brush = new SolidBrush(Color.FromArgb(Brush.A, Brush.R, Brush.G, Brush.B)))
            {
                switch (Type)
                {                      
                    case PolygonType.Triangle:
                        Point[] pointsForTriangle = CreateTtiangle(Center, Radius, Angle);
                        g.FillPolygon(brush, pointsForTriangle);
                        break;
                    case PolygonType.Square:
                        Point[] pointsForSquare = CreateSquare(Center, Radius, Angle);
                        g.FillPolygon(brush, pointsForSquare);
                        break;
                    case PolygonType.Circle:
                        Rectangle rectangleForCicle = CreateCircle(Center, Radius);
                        g.FillEllipse(brush,rectangleForCicle);
                        break;
                    default:
                        break;
                }
            }           
        }

        private Rectangle CreateCircle(Point Center, int Radius)
        {
            Rectangle r = new Rectangle();
            r.Height = 2 * Radius;
            r.Width = 2 * Radius;
            r.X = Center.X - Radius;
            r.Y = Center.Y - Radius;

            return r;
        }

        private Point[] CreateSquare(Point Center, int Radius, double Angle)
        {
            //Строим квадрат без поворота в (0;0)
            Point leftTopInterm = new Point(- Radius,- Radius);
            Point leftBottomInterm = new Point(- Radius, Radius);
            Point rightTopInterm = new Point(Radius,- Radius);
            Point rightBottomInterm = new Point(Radius,Radius);

            //Поворачиваем относительно центра
            Point leftTop = new Point();
            leftTop.X = (int)Math.Round(leftTopInterm.X * Math.Cos(Angle) - leftTopInterm.Y * Math.Sin(Angle))+ Center.X;
            leftTop.Y = (int)Math.Round(leftTopInterm.X * Math.Sin(Angle) + leftTopInterm.Y * Math.Cos(Angle)) + Center.Y;

            Point leftBottom = new Point();
            leftBottom.X = (int)Math.Round(leftBottomInterm.X * Math.Cos(Angle) - leftBottomInterm.Y * Math.Sin(Angle)) + Center.X;
            leftBottom.Y = (int)Math.Round(leftBottomInterm.X * Math.Sin(Angle) + leftBottomInterm.Y * Math.Cos(Angle)) + Center.Y;
            
            Point rightBottom = new Point();
            rightBottom.X = (int)Math.Round(rightBottomInterm.X * Math.Cos(Angle) - rightBottomInterm.Y * Math.Sin(Angle)) + Center.X;
            rightBottom.Y = (int)Math.Round(rightBottomInterm.X * Math.Sin(Angle) + rightBottomInterm.Y * Math.Cos(Angle)) + Center.Y;

            Point rightTop = new Point();
            rightTop.X = (int)Math.Round(rightTopInterm.X * Math.Cos(Angle) - rightTopInterm.Y * Math.Sin(Angle)) + Center.X;
            rightTop.Y = (int)Math.Round(rightTopInterm.X * Math.Sin(Angle) + rightTopInterm.Y * Math.Cos(Angle)) + Center.Y;
        
            Point[] points = new Point[]{leftTop, leftBottom, rightBottom, rightTop };

            return points;

        }

        private Point[] CreateTtiangle(Point Center, int Radius, double Angle)
        {
            //Строим триугольник без поворота в (0;0)
            Point topInterm = new Point(0,-Radius);
            Point rightInterm = new Point((int)Math.Round(Math.Sqrt(3) * Radius /2), Radius /2);
            Point leftInterm = new Point((int)Math.Round(-Math.Sqrt(3) * Radius / 2), Radius / 2);

            //Поворачиваем относительно центра
            Point top = new Point();
            top.X = (int)Math.Round(topInterm.X * Math.Cos(Angle) - topInterm.Y * Math.Sin(Angle)) + Center.X;
            top.Y = (int)Math.Round(topInterm.X * Math.Sin(Angle) + topInterm.Y * Math.Cos(Angle)) + Center.Y;

            Point right = new Point();
            right.X = (int)Math.Round(rightInterm.X * Math.Cos(Angle) - rightInterm.Y * Math.Sin(Angle)) + Center.X;
            right.Y = (int)Math.Round(rightInterm.X * Math.Sin(Angle) + rightInterm.Y * Math.Cos(Angle)) + Center.Y;

            Point left = new Point();
            left.X = (int)Math.Round(leftInterm.X * Math.Cos(Angle) - leftInterm.Y * Math.Sin(Angle)) + Center.X;
            left.Y = (int)Math.Round(leftInterm.X * Math.Sin(Angle) + leftInterm.Y * Math.Cos(Angle)) + Center.Y;

            Point[] points = new Point[] { top, left, right};

            return points;

        }

        public object Clone()
        {
            Polygon newpolygon = new Polygon();
            newpolygon.Type = Type;
            newpolygon.Brush = Brush.Clone() as Brush;
            newpolygon.Angle = Angle;
            newpolygon.Center = new Point(Center.X, Center.Y);
            newpolygon.Radius = Radius;
            
            return newpolygon;
        }

        private Point MutatePoint(Point point, Workarea workarea)
        {
            if (Constants.Rand.NextDouble() <= Constants.MutationPointMoveMaxChance)
            {
                point = new Point(Constants.Rand.Next(0, Constants.Width), Constants.Rand.Next(0, Constants.Height));
                workarea.IsChange = true;
            }

            if (Constants.Rand.NextDouble() <= Constants.MutationPointMoveMiddleChance)
            {
                point.X = Math.Min(Math.Max(0, point.X + Constants.Rand.Next(-Constants.MiddleRange, Constants.MiddleRange + 1)), Constants.Width);
                point.Y = Math.Min(Math.Max(0, point.Y + Constants.Rand.Next(-Constants.MiddleRange, Constants.MiddleRange + 1)), Constants.Height);
                workarea.IsChange = true;
            }

            if (Constants.Rand.NextDouble() <= Constants.MutationPointMoveNearChance)
            {
                point.X = Math.Min(Math.Max(0, point.X + Constants.Rand.Next(-Constants.NearRange, Constants.NearRange + 1)), Constants.Width);
                point.Y = Math.Min(Math.Max(0, point.Y + Constants.Rand.Next(-Constants.NearRange, Constants.NearRange + 1)), Constants.Height);
                workarea.IsChange = true;
            }
            return point;
        }

        private int MutateRadius(int radius, Workarea workarea)
        {
            if (Constants.Rand.NextDouble() <= Constants.MutationRadiusChangeChance)
            {
                radius = Constants.Rand.Next(0, Math.Min(Constants.Height, Constants.Width));
                workarea.IsChange = true;
            }

            if (Constants.Rand.NextDouble() <= Constants.MutationRadiusChangeMiddleChance)
            {               
                // Минимум из меньшей стороны и измененного радиуса
                radius = Math.Min(Math.Max(0, radius + Constants.Rand.Next(-Constants.MiddleRange, Constants.MiddleRange + 1)), Math.Min(Constants.Height, Constants.Width));
                workarea.IsChange = true;
            }

            if (Constants.Rand.NextDouble() <= Constants.MutationRadiusChangeNearChance)
            {
                radius = Math.Min(Math.Max(0, radius + Constants.Rand.Next(-Constants.NearRange, Constants.NearRange + 1)), Math.Min(Constants.Height, Constants.Width));
                workarea.IsChange = true;
            }
            return radius;
        }

        private double MutateAngle(double angle, Workarea workarea)
        {
            if (Constants.Rand.NextDouble() <= Constants.MutationAngleChangeChance)
            {                
                angle = Constants.Rand.NextDouble() * Math.PI * 2;
                workarea.IsChange = true;
            }

            if (Constants.Rand.NextDouble() <= Constants.MutationAngleChangeMiddleChance)
            {
                double additionalAngle  = (Constants.Rand.NextDouble()*2-1)*Constants.MiddleAngleRange;
                
                angle = Math.Min(Math.Max(0,angle+additionalAngle),Math.PI*2);
                workarea.IsChange = true;
            }

            if (Constants.Rand.NextDouble() <= Constants.MutationAngleChangeNearChance)
            {
                double additionalAngle = (Constants.Rand.NextDouble() * 2 - 1) * Constants.NearAngleRange;
                
                angle = Math.Min(Math.Max(0, angle + additionalAngle), Math.PI * 2);
                workarea.IsChange = true;
            }
            return angle;
        }

    }
}
